-
Notifications
You must be signed in to change notification settings - Fork 1
Feat: 온보딩 UI #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: 온보딩 UI #10
Conversation
Walkthrough온보딩 흐름 관련 페이지·컴포넌트·상수·훅과 프로필 업로드 훅 및 로더 모달이 추가·수정되었고, 홈 컴포넌트명 변경 및 일부 CSS 변수 및 package 의존성이 제거되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant OnboardingPage
participant OnboardingStep
participant OnboardingProfileClient
participant UploadButton
participant useProfileImageUpload
User->>OnboardingPage: 온보딩 시작
OnboardingPage->>OnboardingStep: 현재 단계 렌더링
User->>OnboardingStep: 다음 클릭
OnboardingPage->>OnboardingPage: 단계 증가 또는 완료 처리
OnboardingPage->>OnboardingProfileClient: 프로필 단계 진입
User->>OnboardingProfileClient: 이름 입력 및 업로드 모달 열기
OnboardingProfileClient->>UploadButton: 업로드 시트 표시
User->>UploadButton: 앨범 선택 또는 기본 이미지 선택
UploadButton->>useProfileImageUpload: 파일 전달 또는 기본 적용 호출
useProfileImageUpload-->>OnboardingProfileClient: 이미지 URL 및 로딩 상태 반환
OnboardingProfileClient->>User: 미리보기 및 완료(로컬 저장 후 라우팅)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/styles/globals.css (1)
99-120: 중복된 유틸리티 블록을 제거해야 합니다.99-120번 줄의
@layer utilities블록이 50-97번 줄의 내용과 완전히 중복됩니다..scrollbar-hide와.bg-radial-yellowgreen-mintgreen유틸리티가 두 번 정의되어 있습니다.중복된 블록을 제거하고 새로운 유틸리티(
.bg-dim-01,.backdrop-blur-7)만 122-130번 줄의 별도 블록에 유지하는 것이 좋습니다.🔎 수정 제안
-@layer utilities { - .scrollbar-hide { - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; /* Firefox */ - } - .scrollbar-hide::-webkit-scrollbar { - display: none; /* Chrome, Safari, Opera*/ - } - .bg-radial-yellowgreen-mintgreen { - background: - radial-gradient( - 39.1% 31.12% at 80.25% 2.51%, - rgba(246, 248, 173, 0.42) 57.26%, - rgba(255, 255, 255, 0.42) 100% - ), - radial-gradient( - 39.99% 49.21% at 23.64% 2.72%, - rgba(66, 240, 158, 0.66) 21.15%, - rgba(255, 255, 255, 0.87) 93.53% - ); - } -} - @layer utilities { .bg-dim-01 { background-color: var(--color-dim-01); } .backdrop-blur-7 { backdrop-filter: blur(7px); } }
🧹 Nitpick comments (11)
src/components/onboarding/ProgressDots.tsx (1)
10-23: className 중복 코드 리팩토링 고려조건부 className에서
h-2.5 w-2.5 rounded-full이 양쪽 분기에 중복됩니다. 가독성과 유지보수성 향상을 위해 공통 클래스를 분리하는 것을 고려해보세요.🔎 제안하는 리팩토링
{Array.from({ length: total }).map((_, i) => { const isActive = i <= current; return ( <span key={i} - className={ - isActive - ? "bg-mint-01 h-2.5 w-2.5 rounded-full" - : "bg-neutral-08 h-2.5 w-2.5 rounded-full" - } + className={`h-2.5 w-2.5 rounded-full ${ + isActive ? "bg-mint-01" : "bg-neutral-08" + }`} /> ); })}src/components/common/NameInput.tsx (1)
17-22: 매직 넘버 문서화 권장라인 21의
36은 입력 필드의 패딩값으로 추정되지만 명확하지 않습니다. 코드 가독성을 위해 상수로 추출하거나 주석을 추가하는 것을 고려해보세요.🔎 제안하는 개선
+const PADDING_WIDTH = 36; // px-4 (16px * 2) + border/extra spacing + const NameInput = ({ value, onChange }: NameInputProps) => { const spanRef = useRef<HTMLSpanElement>(null); const [inputWidth, setInputWidth] = useState<number>(INITIAL_WIDTH); useEffect(() => { if (!spanRef.current) return; const spanWidth = spanRef.current.offsetWidth; - setInputWidth(spanWidth + 36); + setInputWidth(spanWidth + PADDING_WIDTH); }, [value]);src/components/onboarding/OnboardingStep.tsx (2)
18-18: 모바일 뷰포트 높이 처리 개선 고려
h-screen대신h-dvh사용을 고려해보세요. 모바일 브라우저의 주소 표시줄로 인한 레이아웃 이슈를 방지할 수 있습니다. 상위 온보딩 페이지(src/app/onboarding/page.tsx)에서 이미min-h-dvh를 사용하고 있어 일관성 측면에서도 유리합니다.🔎 제안하는 수정
- <main className="relative flex h-screen flex-col bg-white pt-[558px]"> + <main className="relative flex h-dvh flex-col bg-white pt-[558px]">
19-33: 배경 오프셋 매직 넘버 상수화 고려
-translate-y-[110px]가 라인 25와 30에서 중복됩니다. 상수로 추출하거나 공통 클래스로 정의하면 유지보수가 용이해집니다.🔎 제안하는 개선
const OnboardingStep = ({ stepName, isLast, onNext }: Props) => { const { title, background } = onboardingContents[stepName]; + const backgroundOffsetClass = "-translate-y-[110px]"; return ( <main className="relative flex h-screen flex-col bg-white pt-[558px]"> {background.type === "image" && ( <Image src={background.src} alt="" fill priority - className="pointer-events-none -translate-y-[110px] object-cover object-top" + className={`pointer-events-none ${backgroundOffsetClass} object-cover object-top`} /> )} {background.type === "class" && ( <div - className={`pointer-events-none absolute -translate-y-[110px] ${background.className}`} + className={`pointer-events-none absolute ${backgroundOffsetClass} ${background.className}`} aria-hidden /> )}src/config/onboardingSteps.ts (1)
1-6: 타입 정의가 잘 구성되어 있습니다.
as const단언을 사용한 튜플 정의와OnboardingStepName타입 추출 패턴이 적절합니다. 다만,OnboardingBackground의type: "class"변형은 현재 사용되지 않고 있습니다. 향후 확장을 위한 것이라면 괜찮지만, 사용 계획이 없다면 YAGNI 원칙에 따라 제거를 고려해볼 수 있습니다.src/components/onboarding/UploadAlert.tsx (3)
28-34:onClose()호출이 중복될 수 있습니다.
handleFileChange에서onSelectAlbum(file)호출 후onClose()를 호출하고 있지만,OnboardingProfile.tsx의onSelectAlbum콜백에서도 이미setIsUploadOpen(false)를 호출합니다. 중복 호출은 현재 문제가 되지 않지만, 한 곳에서만 닫기 로직을 관리하는 것이 좋습니다.또한, 동일한 파일을 다시 선택할 경우
onChange이벤트가 발생하지 않습니다. input의 value를 초기화해야 합니다.🔎 수정 제안
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; - if (!file) return; + if (!file) return; + + e.target.value = ""; // 동일 파일 재선택 허용 onSelectAlbum(file); - onClose(); + // onClose는 부모에서 처리 };
56-62: 파일 크기 검증이 없습니다.
accept="image/*"로 이미지 파일만 허용하고 있지만, 파일 크기에 대한 제한이 없습니다. 대용량 이미지 업로드 시 성능 문제나 서버 오류가 발생할 수 있으므로, 클라이언트 측에서 파일 크기를 검증하는 것을 권장합니다.
36-43: 접근성 개선을 고려해보세요.모달에
role="dialog",aria-modal="true",aria-labelledby속성이 없습니다. 또한 모달이 열릴 때 포커스 트래핑이 구현되어 있지 않아 키보드 사용자의 접근성이 제한될 수 있습니다.src/components/common/AlertModal.tsx (3)
38-43: 배경 클릭으로 모달이 닫히지 않습니다.
UploadAlert은 배경 클릭 시onClose를 호출하지만,AlertModal은 배경 클릭 핸들러가 없습니다. 컴포넌트 간 일관성을 위해 배경 클릭 동작을 통일하거나,closeOnBackdropprop을 추가하여 제어하는 것을 고려해보세요.
53-65: 로딩 상태와 메시지 렌더링 로직이 명확합니다.
isLoading && loadingImageSrc조건으로 로딩 이미지를 표시하고, 그렇지 않으면message를 표시하는 로직이 잘 구성되어 있습니다. 다만isLoading이true이지만loadingImageSrc가 없는 경우 아무것도 표시되지 않으므로, 이 케이스에 대한 폴백(예: 기본 스피너)을 고려해볼 수 있습니다.
54-58:<img>태그 대신 Next.js의Image컴포넌트 사용을 고려해보세요.Next.js 프로젝트에서는 이미지 최적화를 위해
next/image의Image컴포넌트를 사용하는 것이 권장됩니다. 단, GIF 파일의 경우unoptimizedprop이 필요할 수 있습니다.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
public/images/onboarding-step-1.pngis excluded by!**/*.pngpublic/images/onboarding-step-2.pngis excluded by!**/*.pngpublic/images/onboarding-step-3.pngis excluded by!**/*.pngpublic/loading.gifis excluded by!**/*.gifsrc/assets/onboarding_profile.svgis excluded by!**/*.svgsrc/assets/photo.svgis excluded by!**/*.svg
📒 Files selected for processing (16)
src/app/login/page.tsxsrc/app/onboarding/page.tsxsrc/app/onboarding/profile/page.tsxsrc/app/page.tsxsrc/components/common/AlertModal.tsxsrc/components/common/NameInput.tsxsrc/components/onboarding/OnboardingProfile.tsxsrc/components/onboarding/OnboardingStep.tsxsrc/components/onboarding/ProfileImagePicker.tsxsrc/components/onboarding/ProgressDots.tsxsrc/components/onboarding/UploadAlert.tsxsrc/components/password/PasswordForm.tsxsrc/config/onboardingSteps.tssrc/hooks/useFunnel.tsxsrc/hooks/useProfileImageUpload.tssrc/styles/globals.css
💤 Files with no reviewable changes (1)
- src/app/login/page.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
src/components/onboarding/OnboardingProfile.tsx (1)
src/hooks/useProfileImageUpload.ts (1)
useProfileImageUpload(5-30)
src/components/onboarding/OnboardingStep.tsx (1)
src/config/onboardingSteps.ts (2)
OnboardingStepName(2-2)onboardingContents(8-36)
src/app/onboarding/page.tsx (3)
src/hooks/useFunnel.tsx (1)
useFunnel(12-36)src/config/onboardingSteps.ts (2)
ONBOARDING_STEPS(1-1)OnboardingStepName(2-2)src/components/onboarding/ProgressDots.tsx (1)
ProgressDots(6-27)
🔇 Additional comments (9)
src/hooks/useProfileImageUpload.ts (1)
1-1: 클라이언트 컴포넌트 지시어가 적절하게 사용되었습니다.이 훅은 React 상태를 사용하므로 "use client" 지시어가 올바르게 적용되었습니다.
src/styles/globals.css (2)
46-47: 새로운 dim 색상 변수가 적절하게 추가되었습니다.
--color-dim-00과--color-dim-01변수는 온보딩 UI의 반투명 배경 스타일링에 사용될 것으로 보이며, 기존 색상 팔레트와 일관성을 유지하고 있습니다.
122-130: 새로운 유틸리티 클래스가 잘 추가되었습니다.
.bg-dim-01과.backdrop-blur-7유틸리티는 모달과 온보딩 UI의 배경 효과를 위해 적절하게 정의되었습니다. AlertModal의 backdrop 기능과 잘 연동될 것으로 보입니다.src/components/password/PasswordForm.tsx (1)
225-225: AlertModal에 backdrop prop이 적절하게 추가되었습니다.새로운 backdrop 기능을 지원하기 위해
backdrop="default"prop이 추가되었습니다. 이는 AlertModal 컴포넌트의 업데이트된 API와 일치하며, 기존 모달 동작에 영향을 주지 않습니다.src/app/onboarding/profile/page.tsx (1)
1-6: 온보딩 프로필 페이지가 올바르게 구현되었습니다.Next.js 페이지 라우팅 규칙에 맞게 간결하게 구현되었으며, OnboardingProfileClient 컴포넌트를 적절하게 래핑하고 있습니다.
src/app/onboarding/page.tsx (1)
24-33: localStorage 키는 일관되게 사용 중라인 28에서 사용하는
"onboardingDone"키는 코드베이스 전체에서 유일하게 한 번만 사용됩니다. 다른 변형 형태(예:"onboarding-done","onboarding_done")는 발견되지 않았으므로 키 일관성 문제는 없습니다.src/config/onboardingSteps.ts (1)
8-36: LGTM!
Record<OnboardingStepName, ...>타입을 사용하여 모든 스텝에 대한 콘텐츠가 정의되어야 함을 컴파일 타임에 보장하고 있습니다. 이미지 경로가 하드코딩되어 있으므로 해당 이미지 파일들이/public/images/디렉토리에 존재하는지 확인해주세요.src/components/onboarding/OnboardingProfile.tsx (1)
49-52: 이미지 선택 후 즉시 모달을 닫는 것이 UX에 적합한지 확인이 필요합니다.
onSelectAlbum에서uploadImage(file)를 호출한 직후setIsUploadOpen(false)로 모달을 닫고 있습니다.uploadImage는 비동기 함수이므로 업로드가 완료되기 전에 모달이 닫히며, 이후 별도의 로딩 모달이 표시됩니다. 현재 흐름이 의도된 것인지 확인해주세요.src/hooks/useFunnel.tsx (1)
3-6:StepProps의name속성이Step컴포넌트에서 사용되지 않습니다.
StepProps인터페이스에name이 정의되어 있지만,Step컴포넌트는children만 사용합니다. 이는 의도된 설계로 보이며(Funnel에서props.name으로 필터링), 타입 정의가 정확합니다.
lemoncurdyogurt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사소한 디자인 오류라던가, 퍼널로직 수정에 대해서 리뷰적어두었습니다!
dim관련해서 이전에 말했던 것이라서 이번에는 수정 꼭해주세요...!
…into feat/6-onboarding
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/styles/globals.css (1)
45-107: 중복된 @layer utilities 블록 제거 필요45-84번 줄과 86-107번 줄에 동일한 유틸리티 클래스가 중복 정의되어 있습니다. 이는 유지보수성을 저해하고 예상치 못한 스타일 충돌을 야기할 수 있습니다.
🔎 중복 제거 제안
-@layer utilities { - .scrollbar-hide { - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; /* Firefox */ - } - .scrollbar-hide::-webkit-scrollbar { - display: none; /* Chrome, Safari, Opera*/ - } - .bg-radial-yellowgreen-mintgreen { - background: - radial-gradient( - 39.1% 31.12% at 80.25% 2.51%, - rgba(246, 248, 173, 0.42) 57.26%, - rgba(255, 255, 255, 0.42) 100% - ), - radial-gradient( - 39.99% 49.21% at 23.64% 2.72%, - rgba(66, 240, 158, 0.66) 21.15%, - rgba(255, 255, 255, 0.87) 93.53% - ); - } -}
🤖 Fix all issues with AI Agents
In @package.json:
- Line 20: The dependency "@use-funnel/next" is a pre-1.0 replacement for the
deprecated "@toss/use-funnel"; before merging, verify migration compatibility by
running full integration and E2E tests against the app, add a CI job that
exercises funnel-related flows, pin the dependency (remove caret) or add a
shrinkwrap/lockfile policy to avoid accidental minor bumps, and document the
change and monitoring plan (including rollback steps) in the PR/README to ensure
safe production rollout.
In @src/app/page.tsx:
- Around line 1-4: RootPage currently does an unconditional redirect to "/login"
which blocks authenticated users and can cause redirect loops; update the server
component RootPage to check authentication (e.g., call getSession() or your auth
check) and perform conditional redirects: if no session redirect("/login"), if
session but onboarding incomplete redirect("/onboarding"), otherwise
redirect("/home"); reference and replace the current redirect call in RootPage
and use getSession (or your session-fetching helper) to determine the correct
branch.
In @src/components/common/LoadingModal.tsx:
- Line 13: The LoadingModal component declares and destructures a
loadingImageSrc prop but never uses it; remove this unused prop by deleting
loadingImageSrc from the props interface (e.g., LoadingModalProps) and from the
component parameter destructuring in the LoadingModal function, and remove any
related default or import usage; alternatively, if you intend to support a
custom image, implement usage by rendering an <img> or background using
loadingImageSrc inside LoadingModal instead—choose one approach and ensure no
references to loadingImageSrc remain elsewhere.
In @src/components/onboarding/UploadButton.tsx:
- Around line 28-34: handleFileChange currently reads the selected file and
calls onSelectAlbum/onClose but doesn't reset the file input, so selecting the
same file again won't trigger onChange; after processing the file (after
onSelectAlbum and before/after onClose) clear the input's value — locate the
input element referenced by handleFileChange (e.g., the HTMLInputElement from
the event) and set its value = '' (or use a ref to the input and reset
ref.current.value = '') so the input is reinitialized for subsequent same-file
selections.
In @src/hooks/useOnboardingFunnel.ts:
- Line 3: The import of useFunnel from "@use-funnel/next" in
useOnboardingFunnel.ts is incompatible with React 19 required by Next.js 15;
either (A) switch the import to "@use-funnel/browser" in useOnboardingFunnel.ts
when using the App Router to avoid the Next-specific package, (B) upgrade or
replace the dependency with a React 19‑compatible release of @use-funnel (check
npm/changelog and update package.json and lockfile), or (C) add targeted
compatibility tests for useFunnel (invoke the useFunnel hook inside the
onboarding hook and run the app/router integration tests) to validate it works
under React 19—choose the appropriate option and update the import/reference to
useFunnel (or the dependency version) accordingly.
In @src/hooks/usePasswordValidation.ts:
- Around line 7-9: The isValidPassword function was reduced to only checking
length which removes required checks for letters, digits, and special
characters; restore the stronger validation used in the commented code by
updating isValidPassword to enforce min length 8 plus at least one
lowercase/uppercase letter, one digit and one special character (use the same
regex/logic from the commented block), ensure the signup flow still calls this
function, and if a weaker check is needed for tests only gate it behind an
explicit TEST env flag or test helper so production logic remains strict.
In @src/hooks/useProfileImageUpload.ts:
- Around line 22-30: Remove the commented-out uploadImage function block in
useProfileImageUpload (the commented async uploadImage / setIsLoading /
uploadImageFile / setProfileImage / finally lines) to clean up unused commented
code; if you want to preserve the snippet for future reference, rely on Git
history or move it to a docs/snippets file, but do not keep it commented in the
hook source.
- Line 5: Remove the unused commented import in useProfileImageUpload: delete
the commented line referencing uploadImageFile so the file no longer contains a
stale "//import { uploadImageFile } from "@/utils/upload";" comment; if/when the
uploadImageFile utility is needed later, re-add it as an active import.
- Line 42: The function useProfileImageUpload contains a duplicated, unreachable
return (the second "return { profileImage, isLoading, uploadImage, resetImage }
as const;" after the earlier return); remove the redundant trailing return so
only the intended single return in useProfileImageUpload remains, ensuring no
other logic depends on the removed line.
In @src/pages/_app.tsx:
- Line 2: The import name is misspelled: change the Next.js font import from
localfont to localFont in the import declaration (the symbol currently imported
as localfont in src/pages/_app.tsx) and update any usages of the variable
(references to localfont throughout the file, e.g., where the font is used or
its className is referenced) to use localFont so the correct Next.js API name is
used consistently.
- Around line 6-11: The pretendard font is being redefined here via the
localfont(...) call (const pretendard) even though the same font is already
exported from src/styles/font.ts; remove this duplicate localfont definition and
instead import the existing exported symbol (e.g., pretendard) from your styles
module and use that imported constant in the _app component so only the single
exported font definition from styles/font.ts is used across the app.
In @src/styles/font.ts:
- Line 1: The import identifier is misspelled as localfont; update the import to
use the correct camelCase name localFont (import localFont from
"next/font/local") and rename any usages of localfont in this file (e.g., calls
like localfont({...}) or references to the variable) to localFont so TypeScript
compiles without error.
In @src/styles/globals.css:
- Around line 109-127: The .loader rule uses hardcoded colors; replace the
literal hex values (border: 5px solid #ffffff and border-bottom-color: #42f09e)
with the project theme CSS variables defined under @theme (e.g., use
var(--<appropriate-theme-bg>) for the border and
var(--<appropriate-theme-accent>) for the highlight) and move the .loader and
@keyframes rotation definitions into the @layer utilities block to keep
consistency with other utility classes; ensure the variable names match the
existing @theme names and update any references accordingly.
🧹 Nitpick comments (8)
src/hooks/usePasswordValidation.ts (1)
2-6: 주석 처리된 코드를 제거하는 것을 고려하세요.사용하지 않는 주석 처리된 코드는 코드베이스를 복잡하게 만들고 향후 혼란을 초래할 수 있습니다. 이전 로직이 필요한 경우 Git 히스토리에서 확인할 수 있습니다.
🔎 제안된 수정 사항
- // const isValidPassword = (pwd: string) => { - // const hasLetter = /[A-Za-z]/.test(pwd); - // const hasNumber = /\d/.test(pwd); - // const hasSpecial = /[^A-Za-z0-9]/.test(pwd); - // return pwd.length >= 8 && hasLetter && hasNumber && hasSpecial; const isValidPassword = (pwd: string) => { return pwd.length >= 8; };src/components/onboarding/UploadButton.tsx (1)
39-39: Tailwind 클래스 사용을 권장합니다.인라인 스타일 대신 Tailwind 클래스를 사용하여 일관성을 유지하는 것을 권장합니다.
🔎 수정 제안
<div className="fixed inset-0 z-50 flex items-end justify-center" - style={{ backgroundColor: "rgba(36, 38, 40, 0.3)" }} + className="fixed inset-0 z-50 flex items-end justify-center bg-black/30" onClick={onClose}src/hooks/useOnboardingFunnel.ts (1)
7-7: 타입 안전성 개선을 고려하세요.
OnboardingFunnelContext가Record<OnboardingStepName, object>로 정의되어 각 스텝의 컨텍스트가 제네릭한object타입입니다. 현재는 빈 객체{}만 사용하므로 문제없지만, 향후 각 스텝별로 특정 데이터를 저장해야 할 경우 타입 안전성이 보장되지 않습니다.필요 시 각 스텝별 구체적인 타입을 정의하는 것을 고려하세요.
src/pages/onboarding/index.tsx (1)
28-28: localStorage 키를 상수로 추출하는 것을 고려하세요.
"onboardingDone"문자열이 하드코딩되어 있습니다. 다른 곳에서도 이 키를 사용할 가능성이 있다면 상수로 추출하여 재사용성과 유지보수성을 높이는 것이 좋습니다.🔎 제안하는 리팩토링
상수 파일 또는 해당 파일 상단에 추가:
const ONBOARDING_DONE_KEY = "onboardingDone";사용:
- localStorage.setItem("onboardingDone", "true"); + localStorage.setItem(ONBOARDING_DONE_KEY, "true");src/components/onboarding/OnboardingStep.tsx (2)
21-21: 매직 넘버를 상수로 추출하는 것을 고려하세요.
w-[440px],pt-[558px],mt-[13px]등 여러 하드코딩된 수치가 있습니다. 이들을 상수로 정의하면 유지보수성과 가독성이 향상됩니다. 또한 디자인 시스템 토큰으로 관리할 수 있다면 더욱 좋습니다.🔎 제안하는 리팩토링
const ONBOARDING_CONTAINER_WIDTH = 440; const ONBOARDING_CONTENT_PADDING_TOP = 558; const ONBOARDING_MARGIN_TOP = 13;또는 Tailwind config에 커스텀 스페이싱으로 추가하여 재사용 가능하게 만들 수 있습니다.
34-34: className 조합 시 유틸리티 함수 사용을 고려하세요.템플릿 리터럴로 className을 직접 연결하는 것보다
clsx또는 프로젝트에서 사용 중인cn유틸리티를 활용하면 조건부 클래스 처리와 가독성이 개선됩니다.🔎 제안하는 리팩토링
- className={`pointer-events-none absolute -translate-y-[110px] ${background.className}`} + className={cn("pointer-events-none absolute -translate-y-[110px]", background.className)}src/constants/onboardingSteps.ts (2)
5-9: 타입 중복을 확인하세요.
OnboardingFunnelSteps타입이src/hooks/useOnboardingFunnel.ts의OnboardingFunnelContext와 유사한 구조를 가지고 있습니다. 두 타입이 동일한 목적을 가진다면 하나로 통합하여 중복을 제거하는 것을 고려하세요.
11-13: OnboardingBackground의 "class" 타입이 현재 사용되지 않습니다.
OnboardingBackground에서type: "class"옵션을 정의했지만, 현재 모든 스텝이type: "image"만 사용하고 있습니다. 향후 확장을 위한 것이라면 문제없지만, 사용하지 않는다면 제거를 고려하세요.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
public/images/onboarding-step-1.svgis excluded by!**/*.svgpublic/images/onboarding-step-2.svgis excluded by!**/*.svgpublic/images/onboarding-step-3.svgis excluded by!**/*.svgyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (25)
package.jsonsrc/app/page.tsxsrc/app/signup/agree/page.tsxsrc/app/signup/password/page.tsxsrc/components/common/LoadingModal.tsxsrc/components/onboarding/OnboardingProfile.tsxsrc/components/onboarding/OnboardingStep.tsxsrc/components/onboarding/ProfileImagePicker.tsxsrc/components/onboarding/ProgressDots.tsxsrc/components/onboarding/UploadButton.tsxsrc/components/password/PasswordForm.tsxsrc/components/signup/ProfileForm.tsxsrc/constants/agreement.tssrc/constants/alert.tssrc/constants/onboardingSteps.tssrc/hooks/useOnboardingFunnel.tssrc/hooks/usePasswordValidation.tssrc/hooks/useProfileImageUpload.tssrc/pages/_app.tsxsrc/pages/onboarding/index.tsxsrc/pages/onboarding/profile.tsxsrc/styles/font.tssrc/styles/globals.csssrc/types/agreement.type.tssrc/utils/upload.ts
✅ Files skipped from review due to trivial changes (1)
- src/utils/upload.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- src/components/onboarding/ProfileImagePicker.tsx
- src/components/onboarding/ProgressDots.tsx
- src/components/onboarding/OnboardingProfile.tsx
- src/components/password/PasswordForm.tsx
🧰 Additional context used
🧬 Code graph analysis (5)
src/constants/agreement.ts (1)
src/types/agreement.type.ts (1)
AgreementKey(1-7)
src/pages/_app.tsx (1)
src/styles/font.ts (1)
pretendard(3-8)
src/hooks/useOnboardingFunnel.ts (1)
src/constants/onboardingSteps.ts (1)
OnboardingStepName(3-3)
src/app/signup/agree/page.tsx (2)
src/types/agreement.type.ts (1)
AgreementKey(1-7)src/constants/agreement.ts (1)
INITIAL_AGREEMENTS(12-19)
src/components/onboarding/OnboardingStep.tsx (1)
src/constants/onboardingSteps.ts (2)
OnboardingStepName(3-3)ONBOARDING_CONTENTS(15-43)
🪛 Biome (2.1.2)
src/hooks/useProfileImageUpload.ts
[error] 42-42: This code will never be reached ...
... because this statement will return from the function beforehand
(lint/correctness/noUnreachable)
🔇 Additional comments (12)
src/constants/agreement.ts (1)
1-19: LGTM! 타입 안전한 agreement 상수 정의타입 정의와 상수가 잘 분리되어 있고, 타입 안전성이 보장됩니다.
src/components/signup/ProfileForm.tsx (1)
12-12: LoadingModal 컴포넌트 인터페이스 확인 완료
LoadingModal컴포넌트는isOpen,title,message,onCloseprops를 모두 지원합니다. ProfileForm.tsx에서도 이 모든 props를 올바르게 전달하고 있으므로 호환성 문제가 없습니다.src/types/agreement.type.ts (1)
1-7: LGTM!타입 정의가 깔끔하고 각 약관의 필수/선택 여부가 주석으로 잘 문서화되어 있습니다.
src/constants/alert.ts (1)
1-7: LGTM!백드롭 타입과 클래스 매핑이 잘 구조화되어 있습니다.
Record타입을 사용하여 타입 안전성을 보장하고 있습니다.src/app/signup/agree/page.tsx (1)
12-19: LGTM!상수와 타입을 중앙 집중화하여 코드 중복을 제거한 좋은 리팩토링입니다.
INITIAL_AGREEMENTS를 초기 상태로 사용하는 것은useState가 내부적으로 값을 복사하므로 안전합니다.src/components/common/LoadingModal.tsx (1)
44-52: LGTM!로딩 상태와 메시지 렌더링 조건 분기가 적절합니다.
loaderCSS 클래스가globals.css(109-114행)에 정의되어 있습니다.src/app/signup/password/page.tsx (1)
137-137: 변경사항이 적절합니다.회원가입 완료 후 온보딩 페이지로 이동하도록 내비게이션 경로가 올바르게 업데이트되었습니다.
src/pages/onboarding/profile.tsx (1)
1-6: 구현이 적절합니다.Next.js 페이지 패턴을 올바르게 따르고 있으며, 온보딩 프로필 컴포넌트를 깔끔하게 래핑하고 있습니다.
src/hooks/useProfileImageUpload.ts (1)
11-20: 이전 리뷰에서 지적된 여러 문제를 해결해주세요.
uploadImage함수에는 다음과 같은 문제가 있습니다:
- 메모리 누수:
URL.createObjectURL()로 생성된 Object URL이 해제되지 않습니다- 파일 검증 부재: 파일 타입과 크기 제한이 없습니다
- 에러 처리 누락: 업로드 실패 시 처리가 없습니다
- 시뮬레이션 딜레이: 2초 지연은 실제 API 연동 전 임시 코드로 보입니다
이전 리뷰 코멘트에서 제공된 개선 방안을 참고하여 수정해주세요.
Likely an incorrect or invalid review comment.
src/pages/onboarding/index.tsx (1)
37-67: 구현이 잘 되어 있습니다.페이지 구조와 퍼널 패턴 사용이 적절하며, 각 스텝 컴포넌트에 필요한 props를 올바르게 전달하고 있습니다.
src/components/onboarding/OnboardingStep.tsx (1)
39-47: 콘텐츠 레이아웃과 버튼 로직이 잘 구현되어 있습니다.
z-10을 사용하여 배경 위에 콘텐츠를 올바르게 배치했고,whitespace-pre-line으로 타이틀의 줄바꿈을 지원하며,isLastStep에 따라 버튼 텍스트를 적절하게 변경하고 있습니다.src/constants/onboardingSteps.ts (1)
24-40: 모든 온보딩 이미지 파일이 정상적으로 존재합니다.
/images/onboarding-step-{1,2,3}.svg경로는 모두public/images/디렉토리에 존재하며, Next.js의 정적 자산 서빙 방식에 맞게 올바르게 구성되어 있습니다. 런타임에 404 오류가 발생하지 않습니다.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🤖 Fix all issues with AI Agents
In @src/app/page.tsx:
- Around line 1-2: HomePage is unprotected: add an authentication guard that
ties into the existing onboarding flow under src/pages/onboarding and
src/components/onboarding by checking the user's auth/onboarding state and
redirecting to the onboarding route or /login if needed; implement this either
in a middleware (middleware.ts) that protects the root route, in the root/layout
(src/app/layout.tsx) by awaiting/validating session and redirecting, or directly
inside the HomePage component (export default function HomePage) by calling the
onboarding/auth hook and issuing a redirect when unauthenticated or
onboarding-incomplete; ensure the redirect target matches the onboarding flow
and reuse the existing onboarding hooks/components for state checks so the home
page is only accessible after successful auth/onboarding.
In @src/components/onboarding/OnboardingProfile.tsx:
- Around line 70-77: The LoadingModal usage currently passes a no-op onClose={()
=> {}} causing the "취소하기" button to do nothing; either remove the confirmLabel
prop or hide/disable the cancel button when cancellation is not allowed, or
implement actual cancel/upload-abort logic and wire it into the onClose handler
(e.g., call your upload abort function or setLoading(false)); update the
LoadingModal invocation (symbols: LoadingModal, props confirmLabel and onClose)
accordingly and consider removing confirmLabel when showing an uninterruptible
loading state for better UX.
- Around line 26-29: handleCreateProfile currently navigates away without
persisting the entered profile, so capture and save the data before routing: in
handleCreateProfile (and where isNameValid is checked) gather the name and image
values and call the profile persistence method (e.g., an API function like
saveProfile or a Redux/Context action such as dispatch(setProfile(...))) and
await its result; only call router.replace("/login") after a successful save,
show/handle errors on failure, and disable the submit action while the save is
in progress to avoid double submissions.
- Around line 57-68: The onSelectAlbum handler calls uploadImage without
handling errors so the modal closes even on failure; update the onSelectAlbum to
await uploadImage (or handle its Promise), catch any upload errors, keep the
modal open on failure (do not call setIsUploadOpen(false) on error), and surface
the error to the user (e.g., show a toast or set an error state) so failures are
visible; also ensure onSelectDefault still closes via resetImage and
setIsUploadOpen(false).
- Around line 23-24: Remove the unreachable duplicate return in the
useProfileImageUpload hook: inside src/hooks/useProfileImageUpload.ts delete the
second return that repeats the object (the one returning "return { profileImage,
isLoading, uploadImage, resetImage } as const;") so only the first valid "return
{ profileImage, isLoading, uploadImage, resetImage };" remains; verify the hook
still exports the same symbols (profileImage, isLoading, uploadImage,
resetImage).
🧹 Nitpick comments (1)
src/components/onboarding/OnboardingProfile.tsx (1)
23-24: 메모리 누수 방지를 위해 Object URL 정리를 고려하세요.
useProfileImageUpload훅이URL.createObjectURL을 사용하여 이미지 URL을 생성하는데, 이러한 Object URL은 사용 후URL.revokeObjectURL로 정리해야 메모리 누수를 방지할 수 있습니다. 컴포넌트가 언마운트되거나 새 이미지가 업로드될 때 이전 Object URL을 정리하는 것을 권장합니다.
useProfileImageUpload훅에 cleanup 로직을 추가하는 것을 고려해보세요:useEffect(() => { return () => { if (profileImage && profileImage.startsWith('blob:')) { URL.revokeObjectURL(profileImage); } }; }, [profileImage]);
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/app/page.tsxsrc/components/onboarding/OnboardingProfile.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/onboarding/OnboardingProfile.tsx (1)
src/hooks/useProfileImageUpload.ts (1)
useProfileImageUpload(7-43)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/components/common/LoadingModal.tsx (1)
13-13: 주석 처리된 코드를 제거하거나 TODO로 명시하세요.
loadingImageSrcprop이 주석 처리되어 있습니다. 향후 사용 계획이 있다면 TODO 주석으로 명시하고, 그렇지 않다면 완전히 제거하는 것이 코드 가독성과 유지보수성에 더 좋습니다.♻️ 제안: 주석 처리된 코드 제거
interface LoadingModalProps { isOpen: boolean; title: string; message?: string; confirmLabel?: string; onClose: () => void; backdrop?: AlertBackdrop; isLoading?: boolean; - //loadingImageSrc?: string; } const LoadingModal = ({ isOpen, title, message, confirmLabel = "확인", onClose, backdrop = "default", isLoading = false, - //loadingImageSrc, }: LoadingModalProps) => {Also applies to: 24-24
src/app/onboarding/page.tsx (1)
19-21: 불필요한 타입 단언을 제거하세요.
ONBOARDING_STEPS가as const로 선언되어 있고currentStepIndex가 배열 범위 내에서 관리되므로, 타입 단언 없이도 TypeScript가 올바른 타입을 추론할 수 있습니다.♻️ 제안: 타입 단언 제거
- const currentStepName = ONBOARDING_STEPS[ - currentStepIndex - ] as OnboardingStepName; + const currentStepName = ONBOARDING_STEPS[currentStepIndex];
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
src/app/onboarding/page.tsxsrc/app/onboarding/profile/page.tsxsrc/app/page.tsxsrc/components/common/LoadingModal.tsxsrc/components/onboarding/OnboardingProfile.tsxsrc/components/onboarding/OnboardingStep.tsxsrc/components/onboarding/ProgressDots.tsxsrc/components/onboarding/UploadButton.tsxsrc/hooks/useProfileImageUpload.tssrc/styles/globals.css
🚧 Files skipped from review as they are similar to previous changes (6)
- src/app/page.tsx
- src/styles/globals.css
- src/components/onboarding/ProgressDots.tsx
- src/components/onboarding/OnboardingStep.tsx
- src/hooks/useProfileImageUpload.ts
- src/components/onboarding/UploadButton.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
src/app/onboarding/page.tsx (2)
src/constants/onboardingSteps.ts (2)
ONBOARDING_STEPS(1-1)OnboardingStepName(3-3)src/components/onboarding/ProgressDots.tsx (1)
ProgressDots(6-23)
src/components/onboarding/OnboardingProfile.tsx (1)
src/hooks/useProfileImageUpload.ts (1)
useProfileImageUpload(7-53)
src/components/common/LoadingModal.tsx (1)
src/constants/alert.ts (2)
AlertBackdrop(1-1)ALERT_BACKDROP_CLASS(3-7)
🔇 Additional comments (5)
src/app/onboarding/profile/page.tsx (1)
1-6: LGTM!Next.js App Router 패턴을 올바르게 따르는 깔끔한 페이지 래퍼입니다.
src/app/onboarding/page.tsx (1)
24-36: LGTM!
typeof window체크를 통한 SSR 안전 처리와 온보딩 완료 후 프로필 페이지로의 네비게이션 로직이 올바르게 구현되어 있습니다.src/components/onboarding/OnboardingProfile.tsx (3)
26-45: 프로필 저장 로직이 올바르게 구현되었습니다.이전 리뷰에서 지적된 데이터 손실 문제가 해결되었습니다. 저장 성공 후에만 페이지 이동이 발생하며, 에러 처리도 포함되어 있습니다. TODO 주석에 명시된 대로 실제 API 연동 시 이 부분을 교체하시면 됩니다.
76-83: 이미지 업로드 에러 처리가 올바르게 추가되었습니다.이전 리뷰에서 지적된 에러 처리 누락 문제가 해결되었습니다. 업로드 실패 시 모달이 열린 상태로 유지되고 에러가 로깅됩니다.
90-97: 로딩 모달의 취소 기능이 올바르게 구현되었습니다.이전 리뷰에서 지적된 빈
onClose핸들러 문제가 해결되었습니다.cancelUpload함수를 통해 사용자가 업로드를 취소할 수 있습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/components/common/LoadingModal.tsx (1)
42-44: 접근성 개선: 로더에 레이블을 추가하세요.로딩 상태를 나타내는
span요소에 스크린 리더를 위한 접근 가능한 레이블이 없습니다.♻️ 제안하는 접근성 개선
- <span className="loader" /> + <span className="loader" role="status" aria-label="로딩 중" />
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/app/signup/agree/page.tsxsrc/app/signup/password/page.tsxsrc/components/common/LoadingModal.tsxsrc/components/password/PasswordForm.tsxsrc/components/signup/ProfileForm.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/password/PasswordForm.tsx
- src/app/signup/password/page.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/common/LoadingModal.tsx (1)
src/constants/alert.ts (2)
AlertBackdrop(1-1)ALERT_BACKDROP_CLASS(3-7)
🔇 Additional comments (9)
src/app/signup/agree/page.tsx (1)
12-19: 상수 추출 리팩토링이 올바르게 구현됨임포트된
AGREEMENTS,INITIAL_AGREEMENTS,AgreementKey의 구조를 확인한 결과, 모두 예상되는 형태와 일치합니다:
INITIAL_AGREEMENTS는Record<AgreementKey, boolean>타입으로 모든 값이false로 초기화됨AGREEMENTS는{ key: AgreementKey, label: string }[]구조로 6개의 약관을 정의AgreementKey는 "service", "finance", "personalReq", "personalOpt1", "personalOpt2", "marketing"을 포함상수 추출을 통한 DRY 원칙 적용과 타입 안정성이 잘 유지되었습니다.
src/components/signup/ProfileForm.tsx (2)
12-12: LGTM! 컴포넌트 임포트 변경이 올바릅니다.
AlertModal에서LoadingModal로의 임포트 변경이 적절합니다.
248-257: LGTM! 모달 컴포넌트 교체가 적절합니다.
LoadingModal사용이 올바르게 적용되었습니다. 명시적으로 설정하지 않은backdrop및isLoadingprops는 기본값을 사용하며, 이는 인증 결과 표시 용도로 적절합니다.src/components/common/LoadingModal.tsx (6)
3-4: LGTM! 백드롭 상수 임포트가 적절합니다.
ALERT_BACKDROP_CLASS와AlertBackdrop타입 임포트가 올바르게 추가되었습니다.
5-13: LGTM! 인터페이스 확장이 적절합니다.
LoadingModalProps인터페이스가 올바르게 정의되었습니다.message를 선택적으로 만들고backdrop및isLoadingprops를 추가한 것이 적절합니다.
15-23: LGTM! 컴포넌트 시그니처가 올바릅니다.함수 시그니처와 기본값 설정(
backdrop = "default",isLoading = false)이 적절합니다.
28-30: LGTM! 백드롭 클래스 적용이 올바릅니다.
ALERT_BACKDROP_CLASS[backdrop]를 사용한 동적 클래스 적용이 타입 안전하게 구현되었습니다.
67-67: LGTM! Export 문이 올바릅니다.컴포넌트 이름 변경에 맞춰 export가 올바르게 업데이트되었습니다.
42-50:loaderCSS 클래스는src/styles/globals.css의 110-120번 줄에서 정의되어 있으며, 회전 애니메이션과 함께 올바르게 구현되어 있습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @src/components/onboarding/OnboardingProfile.tsx:
- Around line 23-24: The useProfileImageUpload hook leaks blob URLs; ensure any
existing object URL stored in profileImage (or equivalent state) is revoked with
URL.revokeObjectURL before replacing it in uploadImage and before clearing it in
resetImage, and add a useEffect cleanup in useProfileImageUpload that revokes
the current blob URL on unmount; update uploadImage to revoke the previous URL
before setting a new one, update resetImage to revoke then clear the state, and
add a cleanup callback that revokes profileImage when the hook/component
unmounts.
🧹 Nitpick comments (3)
src/components/onboarding/OnboardingProfile.tsx (1)
76-83: 중복된 상태 업데이트.
UploadButton컴포넌트의handleFileChange(line 53)에서 파일 선택 직후onClose()가 이미 호출됩니다. 따라서 line 79의setIsUploadOpen(false)는 중복 호출입니다.♻️ 중복 제거 제안
onSelectAlbum={async file => { try { await uploadImage(file); - setIsUploadOpen(false); } catch (error) { console.error("이미지 업로드 실패:", error); } }}src/components/onboarding/UploadButton.tsx (2)
23-36: 포커스 관리 개선 권장.모달이 열릴 때 컨테이너
div에 포커스를 주고 있지만, 첫 번째 인터랙티브 요소(버튼)에 포커스를 주는 것이 접근성 측면에서 더 좋습니다.♻️ 첫 번째 버튼에 포커스 주기
+const firstButtonRef = useRef<HTMLButtonElement | null>(null); useEffect(() => { if (!isOpen) return; const handleEscape = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; document.addEventListener("keydown", handleEscape); - modalRef.current?.focus(); + firstButtonRef.current?.focus(); return () => { document.removeEventListener("keydown", handleEscape); }; }, [isOpen, onClose]);버튼에 ref 추가:
<button type="button" + ref={firstButtonRef} className="bg-neutral-11 text-button-sb text-mint-01 ..." onClick={handleAlbumClick} >
56-64: 인라인 스타일을 Tailwind로 대체 고려.
backgroundColor가 인라인 스타일로 지정되어 있습니다. 프로젝트 일관성을 위해 Tailwind의bg-black/30또는 커스텀 색상 클래스 사용을 고려해 보세요.♻️ Tailwind 클래스 사용
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/30" - style={{ backgroundColor: "rgba(36, 38, 40, 0.3)" }} onClick={onClose} ... >정확한 색상이 필요하다면
tailwind.config에 커스텀 색상을 추가하세요.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/onboarding/OnboardingProfile.tsxsrc/components/onboarding/OnboardingStep.tsxsrc/components/onboarding/UploadButton.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/onboarding/OnboardingStep.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/components/onboarding/OnboardingProfile.tsx (2)
src/hooks/useProfileImageUpload.ts (1)
useProfileImageUpload(7-53)src/components/common/FullButton.tsx (1)
FullButton(10-35)
src/components/onboarding/UploadButton.tsx (1)
src/components/common/FullButton.tsx (1)
FullButton(10-35)
🔇 Additional comments (6)
src/components/onboarding/OnboardingProfile.tsx (3)
1-14: LGTM!임포트 구조와 클라이언트 컴포넌트 선언이 적절합니다.
43-44: 프로필 생성 후 라우팅 경로 확인 필요.온보딩 프로필 생성 후
/login으로 이동하는 것이 의도된 흐름인지 확인이 필요합니다. 일반적으로 온보딩 완료 후에는 홈 페이지나 다음 단계로 이동하는 것이 자연스럽습니다.
90-97: LGTM!
LoadingModal구성이 적절합니다. 다만cancelUpload가 현재는isLoading상태만 변경하므로, 실제 API 연동 시AbortController등을 활용한 요청 취소 로직이 필요합니다.src/components/onboarding/UploadButton.tsx (3)
1-12: LGTM!클라이언트 컴포넌트 선언과 Props 인터페이스 정의가 명확합니다.
40-54: LGTM!파일 입력 초기화 로직이 적절합니다.
fileInputRef.current.value = ""를 통해 동일한 파일을 다시 선택할 수 있게 처리한 점이 좋습니다.
71-111: LGTM!모달 내부 구조가 깔끔하고
FullButton컴포넌트를 적절히 활용하고 있습니다. ARIA 속성들이 잘 설정되어 있어 접근성이 양호합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/common/FullButton.tsx (1)
16-32:h-14.5는 유효하지 않은 Tailwind 클래스입니다. 일관되게 수정이 필요합니다
h-14.5는 Tailwind v4 기본 스케일에 없으며 설정 파일에도 정의되지 않았습니다. 따라서 버튼 요소에는 높이가 적용되지 않으며, 실제 높이는 내부 div의h-[58px]에만 의존합니다.버튼 요소의 높이를 명확히 보장하려면 다음 중 하나를 선택하세요:
- 버튼과 내부 div 모두에
h-[58px]를 사용 (권장)- 또는 globals.css의
@theme블록에--height-14-5: 3.625rem;같은 커스텀 값 정의
🧹 Nitpick comments (1)
src/components/common/FullButton.tsx (1)
24-33: 불필요한 래퍼 +text-button-sb중복 적용이라 정리 권장버튼 자체가 이미
items-center justify-center인데 내부에 높이 고정 래퍼(div)를 한 번 더 두고,text-button-sb도 stateClass/자식 span에 중복이라 유지보수성이 떨어집니다. (한 군데로 모으는 편이 안전)제안 diff
- const baseClass = - "flex h-14.5 w-full cursor-pointer items-center justify-center rounded-2xl px-3.5"; - const activeClass = "bg-mint-01 text-white text-button-sb"; + const baseClass = + "flex h-[58px] w-full cursor-pointer items-center justify-center rounded-2xl px-3.5"; + const activeClass = "bg-mint-01 text-white"; const inactiveClass = - "border border-neutral-08 bg-white text-neutral-06 text-button-sb"; + "border border-neutral-08 bg-white text-neutral-06"; @@ <button className={`${baseClass} ${stateClass} ${className}`} disabled={!isActive} {...props} > - <div className="flex h-[58px] items-center justify-center"> - <span className="text-button-sb">{children}</span> - </div> + <span className="text-button-sb">{children}</span> </button>
lemoncurdyogurt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저번에 코드리뷰했던 부분 많이 수정해주셔서 감사합니다(_ _) 꾸벅
오늘 말씀드렸던 것처럼 개발자모드로 반응형 화면 고려해서 컴포넌트 너비라던가 높이 관련 요소들 재수정해주시면 감사하겠습니다아..!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
browser라서 일단 funnel/next 코드 지워주세요오...!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
내가 지움Vv
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
감사합니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
src/components/onboarding/UploadButton.tsx (1)
47-54:onSelectAlbum호출 후onClose중복 호출 가능성
onSelectAlbum이 비동기 함수로 전달될 경우,onClose()가 즉시 호출되어 모달이 먼저 닫힙니다. 현재 부모 컴포넌트(OnboardingProfile)에서도setIsUploadOpen(false)를 호출하고 있어 중복 호출이 발생합니다.모달 닫힘 시점을 부모가 제어하도록
onClose()호출을 제거하는 것이 더 명확합니다.♻️ 제안된 수정
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (!file) return; onSelectAlbum(file); e.target.value = ""; - onClose(); };src/components/onboarding/OnboardingProfile.tsx (3)
34-42: 에러 발생 시 사용자 피드백이 없습니다.현재 에러 발생 시
console.error만 호출하고 사용자에게 아무런 피드백을 주지 않습니다. API로 교체 시 사용자에게 실패 메시지를 표시하는 것이 좋습니다.
62-64: 반응형 레이아웃 개선 필요
px-[118px]는 고정된 값으로 작은 화면에서 레이아웃이 깨질 수 있습니다. 비율 기반 패딩이나max-width를 사용하는 것이 더 안정적입니다.♻️ 제안된 수정
- <div className="w-full px-[118px]"> + <div className="mx-auto w-full max-w-[200px]"> <NameInput value={name} onChange={setName} /> </div>
80-87:setIsUploadOpen(false)중복 호출
UploadButton내부에서onSelectAlbum호출 후onClose()를 이미 실행하므로, Line 83의setIsUploadOpen(false)는 중복됩니다.UploadButton에서onClose()호출을 제거하면 이 코드가 의도대로 동작합니다.src/app/signup/agree/page.tsx (1)
12-23:INITIAL_AGREEMENTS초기화 및 필수 동의 키 관리 개선타입 안전성은 이미
AgreementKey명시 지정으로 보장되고 있으나, 다음 두 가지 개선이 권장됩니다:
useState(INITIAL_AGREEMENTS): 상수 객체를 직접 공유하지 말고useState<Record<AgreementKey, boolean>>(() => ({ ...INITIAL_AGREEMENTS }))로 초기화하여 방어적으로 처리isConfirmActive하드코딩 제거: 필수 동의 키 리스트(service,finance,personalRequired)를REQUIRED_AGREEMENT_KEYS같은 상수로 별도 관리하고, 동적으로 검증하면 필수 조건 변경 시 drift 위험 제거최소 개선 방안 (diff)
const [agreements, setAgreements] = - useState<Record<AgreementKey, boolean>>(INITIAL_AGREEMENTS); + useState<Record<AgreementKey, boolean>>(() => ({ ...INITIAL_AGREEMENTS }));그리고
src/constants/agreement.ts에 추가:+ export const REQUIRED_AGREEMENT_KEYS: AgreementKey[] = ['service', 'finance', 'personalRequired'];page.tsx에서:
- const isConfirmActive = - agreements.service && agreements.finance && agreements.personalRequired; + const isConfirmActive = REQUIRED_AGREEMENT_KEYS.every(key => agreements[key]);
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
src/app/login/page.tsxsrc/app/login/phone/page.tsxsrc/app/onboarding/page.tsxsrc/app/password/find/page.tsxsrc/app/password/find/result/page.tsxsrc/app/signup/agree/page.tsxsrc/components/common/NameInput.tsxsrc/components/onboarding/OnboardingProfile.tsxsrc/components/onboarding/OnboardingStep.tsxsrc/components/onboarding/ProfileImagePicker.tsxsrc/components/onboarding/ProgressDots.tsxsrc/components/onboarding/UploadButton.tsxsrc/constants/agreement.tssrc/constants/alert.tssrc/types/agreement.type.ts
🚧 Files skipped from review as they are similar to previous changes (8)
- src/constants/agreement.ts
- src/app/onboarding/page.tsx
- src/components/onboarding/OnboardingStep.tsx
- src/components/onboarding/ProgressDots.tsx
- src/constants/alert.ts
- src/components/onboarding/ProfileImagePicker.tsx
- src/types/agreement.type.ts
- src/components/common/NameInput.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
src/app/signup/agree/page.tsx (2)
src/types/agreement.type.ts (1)
AgreementKey(1-7)src/constants/agreement.ts (1)
INITIAL_AGREEMENTS(12-19)
src/components/onboarding/OnboardingProfile.tsx (3)
src/hooks/useProfileImageUpload.ts (1)
useProfileImageUpload(7-53)src/components/common/NameInput.tsx (1)
NameInput(13-40)src/components/common/FullButton.tsx (1)
FullButton(10-35)
src/components/onboarding/UploadButton.tsx (1)
src/components/common/FullButton.tsx (1)
FullButton(10-35)
🔇 Additional comments (10)
src/app/password/find/page.tsx (1)
7-7: LGTM! 일관된 레이아웃 개선입니다.main 컨테이너에
w-full추가는 반응형 디자인 일관성을 위한 적절한 변경입니다. PR 전반의 레이아웃 리팩토링과 일관성이 있습니다.src/app/login/page.tsx (1)
13-13: LGTM! 일관된 레이아웃 패턴 적용입니다.인증 페이지 전반에 걸쳐 동일한
w-full패턴을 적용하여 일관성 있는 레이아웃을 구현하고 있습니다.src/app/login/phone/page.tsx (1)
30-35: 배경 및 레이아웃 리팩토링이 잘 적용되었습니다.배경 스타일을 outer container로 이동하고 inner wrapper를 full-width로 변경한 것은 반응형 디자인 개선을 위한 적절한 접근입니다. 동적 배경 로직(
isActive조건)도 잘 보존되었습니다.다만, inner wrapper에
px-4패딩이 추가되어 기존w-110에서w-full로 변경되면서 시각적 여백이 달라질 수 있으니, 다양한 화면 크기에서 로그인 폼의 레이아웃이 의도대로 표시되는지 확인해 주세요.src/app/password/find/result/page.tsx (2)
16-16: 레이아웃 개선이 적용되었습니다.main 컨테이너에
h-screen,min-h-dvh,w-full을 함께 적용하여 전체 뷰포트 크기를 확보하고 있습니다.h-screen과min-h-dvh의 조합은 다양한 브라우저/디바이스 환경에서 일관된 높이를 보장하기 위한 것으로 보입니다.
17-17: LGTM! Inner wrapper의 너비 제약이 제거되었습니다.
w-110제약을w-full로 변경하고h-full을 제거하여 더 유연한 레이아웃을 구현하고 있습니다. 컨텐츠가 자연스러운 높이를 가지도록 하는 의도로 보입니다.src/components/onboarding/UploadButton.tsx (2)
23-36: LGTM! 접근성과 키보드 이벤트 처리가 잘 구현되어 있습니다.Escape 키 핸들링과 모달 포커스 관리가 적절합니다. 이벤트 리스너 cleanup도 올바르게 처리되어 있습니다.
56-113: 모달 구조와 접근성 속성이 잘 적용되었습니다.
role="dialog",aria-modal,aria-labelledby등 접근성 속성이 적절히 구현되어 있습니다. 향후 개선 사항으로 포커스 트랩(focus trap)을 추가하면 키보드 사용자 경험이 더 좋아질 수 있습니다.src/components/onboarding/OnboardingProfile.tsx (2)
15-24: 컴포넌트 구조와 상태 관리가 잘 되어 있습니다.커스텀 훅(
useProfileImageUpload)을 활용한 관심사 분리와 상태 관리가 적절합니다.
94-101:andsrc/app/signup/agree/page.tsx (1)
12-15: 이 코멘트는 적용되지 않습니다. 프로젝트의tsconfig.json에서"jsx": "react-jsx"를 사용하고 있으므로, React 네임스페이스 타입이 자동으로 스코프에 포함됩니다. 따라서React.ChangeEvent<HTMLInputElement>사용 시 명시적인 React import가 필요하지 않으며, 현재 코드는 올바릅니다.Likely an incorrect or invalid review comment.
🔥 작업 내용
🤔 추후 작업 사항
📸 작업 내역 스크린샷
🔗 이슈
Summary by CodeRabbit
새로운 기능
개선 사항
기타
✏️ Tip: You can customize this high-level summary in your review settings.